package se.nikmel.AnDisper.DisperServer;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlSerializer;

import se.nikmel.AnDisper.Application.Application;
import se.nikmel.AnDisper.Display.DisperDisplay;
import se.nikmel.AnDisper.Display.DisplaySetup;
import se.nikmel.AnDisper.Display.DisplaySetup.E_CONFIGURATION;
import android.util.Base64;
import android.util.Log;
import android.util.Xml;

/**
 * Protocol definition:
 * 
 * <code>
 * 
 * PING_REQUEST, PING_RESPONSE and CLOSE_CONNECTION are all handles by the Disper_Connection class
 * 
 * Send commands: 
 * 1. PARAMETERLESS 
 * <Message> 
 * 		<Command>
 * 			GET_DISPLAYS | PING_REQUEST | GET_APPLICATIONS | GET_HOSTNAME | CLOSE_CONNECTION
 * 		</Command>
 * </Message>
 * 
 * 2. WITH PARAMS 
 * <Message> 
 * 		<Command> 
 * 			SET_DISPLAYS 
 * 		</Command> 
 * 		<DisplaySetup config="SINGLE" | "CLONE" | "DUAL_HORIZONTAL" | "DUAL_VERTICAL"> 
 * 			<ID/> DFP-0 
 * 			<ID/> DFP-1
 * 		</DisplaySetup> 
 * </Message>
 * 
 * <Message> 
 * 		<Command> 
 * 			REQUEST_APP_ICON  
 * 		</Command>
 * 		<AppIcon/> Icon MD5 
 * </Message>
 * 
 * <Message>
 * 		<Command> 
 * 			LAUNCH_APPLICATION 
 * 		</Command> 
 * 		<ID/> CommandID 
 * </Message>
 * 
 *  
 * 
 * :: RESPONSES
 * 
 * GET_DISPLAYS 
 * <Message> 
 * 		<Response command='GET_DISPLAYS'> 
 * 			<Display> 
 * 				<ID/> LEN
 * 				<Name/> Lenovo LCD DFP-0 
 * 				<Resolutions> 
 * 					<Resolution/> 640x480 
 * 					<Resolution/> 800x600 etc.... 
 * 				</Resolutions> 
 * 			</Display> 
 * 			<Display> etc... 
 * 			</Display>
 * 		</Response> 
 * </Message>
 * 
 * SET_DISPLAYS 
 * 	- No response!
 * 
 * PING_RESPONSE
 * <Message> 
 * 		<Response command='PING_REQUEST'> 
 * 			PING_RESPONSE
 * 		</Response> 
 * </Message>
 * 
 * GET_APPLICATIONS 
 * <Message> 
 * 		<Response command='GET_APPLICATIONS'> 
 * 			<App> 
 * 				<ID/> [run-command, e.g. /usr/bin/xbmc] 
 * 				<Name/> XBMC Mediacenter 
 * 				<Icon MD5/> <![CDATA[ MD5 fingerprint of Icon ]]>
 * 			</App> 
 * 			<App> etc... 
 * 			</App> 
 * 		</Response>
 * </Message>
 * 
 * REQUEST_APP_ICON 
 * <Message> 
 * 	<Response command='REQUEST_APP_ICON' icon_md5='[md5]'> 
 * 		<Icon>
 * 			<![CDATA[ Base64 encoded data for the icon ]]> 
 * 		</Icon> 
 * 		<IconType/> png | svg | jpg | gif 
 * 	</Response>
 * </Message>
 * 
 * LAUNCH_APPLICATION 
 * 	- No response!
 * 
 * GET_HOSTNAME 
 * <Message> 
 * 		<Response command='GET_HOSTNAME'> 
 * 			<HostName/> LapTop
 * 		</Response> 
 * </Message>
 * 
 * 
 * </code>
 */

public class DisperInterface {
	private static final String TAG = "DisperInterface";

	/**
	 * This class acts as an interface to the Disper-indicator. It provides a
	 * high-abstraction level interface for the app and translates commands etc
	 * to protocol level messages. It uses private class DisperConnection to
	 * send commands and receive resonses TODO Add GET_HOSTNAME,
	 * GET_APPLICATIONS, LAUNCH_APPLICATION, REQUEST_APP_ICON
	 */
	enum E_DISPER_PROTOCOL {
		GET_DISPLAYS, SET_DISPLAYS, CLOSE_CONNECTION, PING_REQUEST, PING_RESPONSE, GET_HOSTNAME, GET_APPLICATIONS, REQUEST_APP_ICON, LAUNCH_APPLICATION;
	};

	// Common tags
	public final static String XML_MESSAGE_TAG = "Message";
	public final static String XML_COMMAND_TAG = "Command";
	public final static String XML_RESPONSE_TAG = "Response";
	public final static String XML_RESPONSE_COMMAND_ATTR = "command";

	// SET_DISPLAYS and GET_DISPLAYS tags
	public final static String XML_DISPLAY_SETUP_TAG = "DisplaySetup";
	public final static String XML_DISPLAY_CONFIG_ATTR = "config";
	public final static String XML_DISPLAY_TAG = "Display";
	public final static String XML_DISPLAY_ID_TAG = "ID";
	public final static String XML_DISPLAY_NAME_TAG = "Name";
	public final static String XML_DISPLAY_RESOLUTION_TAG = "Resolution";
	// GET_HOSTNAME
	public final static String XML_HOSTNAME_TAG = "HostName";
	// GET_APPLICATIONS and REQUEST_APP_ICON
	public final static String XML_APPLICATION_TAG = "App";
	public final static String XML_APP_ID_TAG = "ID";
	public final static String XML_APP_NAME_TAG = "Name";
	public final static String XML_APP_ICON_TAG = "Icon";
	public final static String XML_APP_ICON_MD5_TAG = "Icon_MD5";
	public final static String XML_APP_ICON_MD5_ATTR = "icon_md5";
	public final static String XML_APP_ICON_TYPE_TAG = "IconType";

	private DisperConnection m_cnnDisperConnection;
	private ServerInfo m_oCachedServerInfo;
	private IconCache m_oIconCache; // Must provide the GET_APPS response parser
									// with ref to IconCache...

	private SAXParserFactory m_oXMLParserFactory;
	private SAXParser m_oXMLParser;

	/* *********** CONSTRUCTORS ************ */

	public DisperInterface(IconCache p_oIconCache, String p_sServerIP) {
		// m_cnnDisperConnection = new DisperConnection(p_sServerIP);
		this(p_oIconCache, p_sServerIP, null);
	}

	public DisperInterface(IconCache p_oIconCache, String p_sServerIP,
			Integer p_iSocketNr) {
		m_cnnDisperConnection = new DisperConnection(p_sServerIP, p_iSocketNr);
		m_oIconCache = p_oIconCache;
		m_oCachedServerInfo = null;
	}

	/* *********** PRIVATE HELPERS ************ */

	private XMLReader getXMLReader() throws ParserConfigurationException,
			SAXException {
		if (m_oXMLParserFactory == null) {
			m_oXMLParserFactory = SAXParserFactory.newInstance();
			m_oXMLParser = m_oXMLParserFactory.newSAXParser();
		}

		return m_oXMLParser.getXMLReader();
	}

	/**
	 * Creates one of the parameterless commands
	 * 
	 * @param p_eCommand
	 *            The command to send. Must be one of the parameterless commands
	 * @return The XML string with the protocol message
	 * @throws IllegalArgumentException
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	private String createCommand(E_DISPER_PROTOCOL p_eCommand)
			throws IllegalArgumentException, IllegalStateException, IOException {
		// Sanitize...
		switch (p_eCommand) {
		case CLOSE_CONNECTION:
		case GET_DISPLAYS:
		case PING_REQUEST:
		case GET_HOSTNAME:
		case GET_APPLICATIONS:
			break;

		default:
			throw new RuntimeException(
					"createParamlessCommand got request for a command that requires parameters!");
		}

		XmlSerializer xmlCommand = Xml.newSerializer();
		StringWriter strWriter = new StringWriter();
		xmlCommand.setOutput(strWriter);

		xmlCommand.startDocument("UTF-8", true);
		xmlCommand.startTag("", XML_MESSAGE_TAG);
		xmlCommand.startTag("", XML_COMMAND_TAG);
		xmlCommand.text(p_eCommand.toString());
		xmlCommand.endDocument();

		return strWriter.toString();
	}

	/**
	 * Create the SET_DISPLAYS command
	 * 
	 * @param p_oDisplaySetup
	 *            The DisplaySetup to apply to the server
	 * @return The protocol message corresponding to the command
	 * 
	 * @throws IllegalArgumentException
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	private String createCommand_SetDisplays(DisplaySetup p_oDisplaySetup)
			throws IllegalArgumentException, IllegalStateException, IOException {

		XmlSerializer xmlCommand = Xml.newSerializer();
		StringWriter strWriter = new StringWriter();
		xmlCommand.setOutput(strWriter);

		xmlCommand.startDocument("UTF-8", true);
		xmlCommand.startTag("", XML_MESSAGE_TAG);
		xmlCommand.startTag("", XML_COMMAND_TAG);
		xmlCommand.text(E_DISPER_PROTOCOL.SET_DISPLAYS.toString());
		xmlCommand.endTag("", XML_COMMAND_TAG);

		xmlCommand.startTag("", XML_DISPLAY_SETUP_TAG);
		xmlCommand.attribute("", XML_DISPLAY_CONFIG_ATTR, p_oDisplaySetup
				.getConfiguration().toString()); // "SINGLE" | "DUAL_HORIZONTAL"
													// | "DUAL_VERTICAL"
		if (p_oDisplaySetup.getConfiguration() == E_CONFIGURATION.SINGLE) {
			xmlCommand.startTag("", XML_DISPLAY_ID_TAG);
			xmlCommand.text(p_oDisplaySetup.getLeftOrTopDisplay().getID());
			xmlCommand.endTag("", XML_DISPLAY_ID_TAG);
		} else {
			xmlCommand.startTag("", XML_DISPLAY_ID_TAG);
			xmlCommand.text(p_oDisplaySetup.getLeftOrTopDisplay().getID());
			xmlCommand.endTag("", XML_DISPLAY_ID_TAG);

			xmlCommand.startTag("", XML_DISPLAY_ID_TAG);
			xmlCommand.text(p_oDisplaySetup.getRightOrBottomDisplay().getID());
			xmlCommand.endTag("", XML_DISPLAY_ID_TAG);
		}

		xmlCommand.endDocument();

		return strWriter.toString();
	}

	/**
	 * Create the REQUEST_APP_ICON command
	 * 
	 * @param p_oDisplaySetup
	 *            The DisplaySetup to apply to the server
	 * @return The protocol message corresponding to the command
	 * 
	 * @throws IllegalArgumentException
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	private String createCommand_RequestIcon(String p_sAppID)
			throws IllegalArgumentException, IllegalStateException, IOException {

		XmlSerializer xmlCommand = Xml.newSerializer();
		StringWriter strWriter = new StringWriter();
		xmlCommand.setOutput(strWriter);

		xmlCommand.startDocument("UTF-8", true);
		xmlCommand.startTag("", XML_MESSAGE_TAG);
		xmlCommand.startTag("", XML_COMMAND_TAG);
		xmlCommand.text(E_DISPER_PROTOCOL.REQUEST_APP_ICON.toString());
		xmlCommand.endTag("", XML_COMMAND_TAG);

		xmlCommand.startTag("", XML_APP_ID_TAG);
		xmlCommand.text(p_sAppID);
		xmlCommand.endTag("", XML_APP_ID_TAG);

		xmlCommand.endDocument();

		return strWriter.toString();
	}

	/**
	 * Creates the LAUNCH_APP command
	 * 
	 */
	private String createCommand_LanchApp(String p_sAppID)
			throws IllegalArgumentException, IllegalStateException, IOException {

		XmlSerializer xmlCommand = Xml.newSerializer();
		StringWriter strWriter = new StringWriter();
		xmlCommand.setOutput(strWriter);

		xmlCommand.startDocument("UTF-8", true);
		xmlCommand.startTag("", XML_MESSAGE_TAG);
		xmlCommand.startTag("", XML_COMMAND_TAG);
		xmlCommand.text(E_DISPER_PROTOCOL.LAUNCH_APPLICATION.toString());
		xmlCommand.endTag("", XML_COMMAND_TAG);

		xmlCommand.startTag("", XML_APP_ID_TAG);
		xmlCommand.text(p_sAppID);
		xmlCommand.endTag("", XML_APP_ID_TAG);

		xmlCommand.endDocument();

		return strWriter.toString();
	}

	/* *********** PUBLIC INTERFACE ************ */

	public String getServerIP() {
		return m_cnnDisperConnection.getServerIP();
	}

	public void setServerIP(String p_sIPAddress) {
		m_cnnDisperConnection.setServerIP(p_sIPAddress);
	}

	public boolean isConnected() {

		try {
			synchronized (m_cnnDisperConnection) {
				return m_cnnDisperConnection.isConnected();
			}
		} catch (Exception e) {
			Log.e(TAG,
					"(EE) Exception caught when checking if server is connected",
					e);
			return false;
		}
	}

	public void close() {
		// TODO Make sure all is closed and deallocated
		synchronized (m_cnnDisperConnection) {
			m_cnnDisperConnection.close();
		}
	}

	/**
	 * Asks the server for the currently attached displays
	 * 
	 * @return A list with the currently attached displays or null if something
	 *         went wrong
	 * @throws DisperConnectionException
	 */
	public List<DisperDisplay> getDisplays() {
		// 1. Build a GET_DISPLAYS command
		String sCommand;
		String sResponse;
		try {
			sCommand = createCommand(E_DISPER_PROTOCOL.GET_DISPLAYS);

			// 2. Send command using DisperConnection require response
			synchronized (m_cnnDisperConnection) {
				sResponse = m_cnnDisperConnection.sendWithReply(sCommand);
			}

			Log.v(TAG, "(II) Received response:\n" + sResponse);
			if (sResponse == null) {
				Log.e(TAG, "(EE) getDisplays received null from server!");
				return null;
			}

			// 3. Decode response -> ArrayList<DisperDisplay> and return
			XMLReader xr = getXMLReader();
			_ResponseParser_GET_DISPLAYS oHandler = new _ResponseParser_GET_DISPLAYS();
			xr.setContentHandler(oHandler);
			InputSource is = new InputSource();
			is.setCharacterStream(new StringReader(sResponse));
			xr.parse(is);

			return oHandler.getDisplays();

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Failed to get available displays", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "Failed to get available displays", e);
		} catch (IOException e) {
			Log.e(TAG, "Failed to get available displays", e);
		} catch (SAXException e) {
			Log.e(TAG, "Failed to get available displays", e);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "Failed to get available displays", e);
		}
		return null;
	}

	/**
	 * Instruct the server to apply the supplied display setup
	 * 
	 * @param p_oSetup
	 *            A valid display setup to apply to the server
	 * @throws DisperConnectionException
	 */
	public void setDisplays(DisplaySetup p_oSetup) {
		// 1. Build a SET_DISPLAYS command with PARAM
		String sCommand;
		try {
			sCommand = createCommand_SetDisplays(p_oSetup);
			synchronized (m_cnnDisperConnection) {
				m_cnnDisperConnection.send(sCommand);
			}

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		} catch (IOException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		}
	}

	public String getHostname() {
		String sCommand;

		// User cached serverinfo?
		if (m_oCachedServerInfo != null
				&& m_oCachedServerInfo.getIP().equals(this.getServerIP()))
			return m_oCachedServerInfo.getName();

		try {
			sCommand = createCommand(E_DISPER_PROTOCOL.GET_HOSTNAME);
			String sReply;

			synchronized (m_cnnDisperConnection) {
				sReply = m_cnnDisperConnection.sendWithReply(sCommand);
			}

			if (sReply == null) {
				Log.e(TAG, "(EE) getHostname received null from server!");
				return null;
			}

			// Decode response -> HostName and return
			XMLReader xr = getXMLReader();
			_ResponseParser_GET_HOSTNAME oHandler = new _ResponseParser_GET_HOSTNAME();
			xr.setContentHandler(oHandler);
			InputSource is = new InputSource();
			is.setCharacterStream(new StringReader(sReply));
			xr.parse(is);

			m_oCachedServerInfo = new ServerInfo(this.getServerIP(),
					oHandler.getHostname(), new Date());

			return m_oCachedServerInfo.getName();

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Failed to request hostname", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "Failed to request hostname", e);
		} catch (IOException e) {
			Log.e(TAG, "Failed to request hostname", e);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "Failed to request hostname", e);
		} catch (SAXException e) {
			Log.e(TAG, "Failed to request hostname", e);
		}
		return null;
	}

	/**
	 * Request a list of known applications that can be launched
	 * 
	 * @return
	 * @throws DisperConnectionException
	 */
	public List<Application> getApplications() {
		String sCommand;
		try {
			sCommand = createCommand(E_DISPER_PROTOCOL.GET_APPLICATIONS);
			String sReply;
			synchronized (m_cnnDisperConnection) {
				sReply = m_cnnDisperConnection.sendWithReply(sCommand);
			}

			if (sReply == null) {
				Log.e(TAG, "(EE) getApplications received null from server!");
				return null;
			}
			// Decode response -> HostName and return
			XMLReader xr = getXMLReader();
			_ResponseParser_GET_APPS oHandler = new _ResponseParser_GET_APPS(
					m_oIconCache);
			xr.setContentHandler(oHandler);
			InputSource is = new InputSource();
			is.setCharacterStream(new StringReader(sReply));
			xr.parse(is);

			return oHandler.getApps();

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Failed to request applications", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "Failed to request applications", e);
		} catch (IOException e) {
			Log.e(TAG, "Failed to request applications", e);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "Failed to request applications", e);
		} catch (SAXException e) {
			Log.e(TAG, "Failed to request applications", e);
		}
		return null;
	}

	/**
	 * Requests an application icon from the server. The icon is identified by
	 * an ID provided in the response from getApplications. This method is
	 * provided in order to minimize required bandwidth in getApplications, as
	 * icons should be cached locally.
	 * 
	 * @param p_sAppID
	 *            The AppID for the app which the icon is requested
	 * @return The requested icon or null if not found or something went wrong
	 *         (check logs!)
	 * @throws DisperConnectionException
	 */
	public IconInfo getIcon(String p_sAppID) {
		// 1. Build a REQUEST_APP_ICON command with PARAM
		// 2. Send command using DisperConnection require response
		// sResposeXML = myConnection.send(p_sCommand, true);
		// 3. Decode response -> IconXML
		// 4. Base64-decode -> Picture
		String sCommand;
		Log.v(TAG, "(II) About to request icon");
		try {
			sCommand = createCommand_RequestIcon(p_sAppID);
			String sReply;
			synchronized (m_cnnDisperConnection) {
				sReply = m_cnnDisperConnection.sendWithReply(sCommand);
			}
			if (sReply == null) {
				Log.e(TAG, "(EE) getIcon received null from server!");
				return null;
			}

			// Decode response -> Icon
			XMLReader xr = getXMLReader();
			_ResponseParser_REQUEST_ICON oHandler = new _ResponseParser_REQUEST_ICON();
			xr.setContentHandler(oHandler);
			InputSource is = new InputSource();
			is.setCharacterStream(new StringReader(sReply));
			xr.parse(is);

			Log.v(TAG, "(II) IconRequest response parsed");
			String sIcon_base64 = oHandler.getIcon_Base64();
			Log.v(TAG, "(II) Base64 encoded icon: " + sIcon_base64);
			byte[] imgBytes = Base64.decode(sIcon_base64, Base64.DEFAULT);
			Log.v(TAG, "(II) Finished decoding icon");

			String sMD5 = oHandler.getIcon_MD5();

			return new IconInfo(imgBytes, sMD5);

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "(EE) Failed to request Icon", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "(EE) Failed to request Icon", e);
		} catch (IOException e) {
			Log.e(TAG, "(EE) Failed to request Icon", e);
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "(EE) Failed to request Icon", e);
		} catch (SAXException e) {
			Log.e(TAG, "(EE) Failed to request Icon", e);
		}
		return null;
	}

	/**
	 * Instructs the server to launch the supplied application
	 * 
	 * @param p_appApplication
	 * @throws DisperConnectionException
	 */
	public void launchApp(Application p_appApplication) {
		// 1. Build a SET_DISPLAYS command with PARAM
		String sCommand;
		try {
			sCommand = createCommand_LanchApp(p_appApplication.getID());
			synchronized (m_cnnDisperConnection) {
				m_cnnDisperConnection.send(sCommand);
			}

		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		} catch (IllegalStateException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		} catch (IOException e) {
			Log.e(TAG, "Failed to send set displays command", e);
		}
	}
}

// ------------------------ RESPONSE PARSERS

// The class parses a response from the GET_DISPLAYS command
class _ResponseParser_GET_DISPLAYS extends DefaultHandler {

	private enum E_CURRENT_SECTION {
		Ignore, DisplayID, DisplayName, Resolution
	};

	private String m_sInResponseToTag;
	private E_CURRENT_SECTION p_eCurrentSection;

	private String m_sCurrentID, m_sCurrentName;
	private List<DisperDisplay> m_lstDisplays;

	/**
	 * Returns the resulting list of available displays
	 * 
	 * @return
	 */
	public List<DisperDisplay> getDisplays() {
		// Sanitize response
		if (m_sInResponseToTag
				.equals(DisperInterface.E_DISPER_PROTOCOL.GET_DISPLAYS
						.toString()))
			return m_lstDisplays;
		else
			return null;
	}

	/* ******************** PARSER CALLBACKS ****************** */
	@Override
	public void startDocument() throws SAXException {
		// m_bInReponseTag = false;
		m_lstDisplays = new ArrayList<DisperDisplay>();
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			org.xml.sax.Attributes attributes) throws SAXException {

		if (localName.equals(DisperInterface.XML_RESPONSE_TAG)) {
			m_sInResponseToTag = attributes
					.getValue(DisperInterface.XML_RESPONSE_COMMAND_ATTR);
		} else if (localName.equals(DisperInterface.XML_DISPLAY_ID_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.DisplayID;
		else if (localName.equals(DisperInterface.XML_DISPLAY_NAME_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.DisplayName;
		// else if(localName.equals(XML_DISPLAY_RESOLUTION))
		// p_eCurrentSection = E_CURRENT_SECTION.Resolution;
	}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException {
		if (localName.equals(DisperInterface.XML_DISPLAY_TAG)) {
			// Add the display to the list
			m_lstDisplays.add(new DisperDisplay(m_sCurrentID, m_sCurrentName));
			m_sCurrentID = null;
			m_sCurrentName = null;
		}
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void characters(char ch[], int start, int length) {
		String sText = new String(ch, start, length);
		sText = sText.trim();

		if (p_eCurrentSection == E_CURRENT_SECTION.DisplayID)
			m_sCurrentID = sText;
		else if (p_eCurrentSection == E_CURRENT_SECTION.DisplayName)
			m_sCurrentName = sText;
	}
}

// ---------------------------------------------------------------

// The class parses a response from the GET_HOSTNAME command
class _ResponseParser_GET_HOSTNAME extends DefaultHandler {

	private enum E_CURRENT_SECTION {
		Ignore, Hostname
	};

	private String m_sInResponseToTag;
	private E_CURRENT_SECTION p_eCurrentSection;

	private String m_sHostname;

	/**
	 * Returns the resulting list of available displays
	 * 
	 * @return
	 */
	public String getHostname() {
		// Sanitize response
		if (m_sInResponseToTag
				.equals(DisperInterface.E_DISPER_PROTOCOL.GET_HOSTNAME
						.toString()))
			return m_sHostname;
		else
			return null;
	}

	/* ******************** PARSER CALLBACKS ****************** */
	@Override
	public void startDocument() throws SAXException {
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			org.xml.sax.Attributes attributes) throws SAXException {

		if (localName.equals(DisperInterface.XML_RESPONSE_TAG)) {
			m_sInResponseToTag = attributes
					.getValue(DisperInterface.XML_RESPONSE_COMMAND_ATTR);
		} else if (localName.equals(DisperInterface.XML_HOSTNAME_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.Hostname;
	}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException {
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void characters(char ch[], int start, int length) {
		if (p_eCurrentSection == E_CURRENT_SECTION.Hostname) {
			String sText = new String(ch, start, length);
			m_sHostname = sText.trim();
		}
	}
}

// ---------------------------------------------------------------

// The class parses a response from the GET_APPLICATIONS command
class _ResponseParser_GET_APPS extends DefaultHandler {

	private enum E_CURRENT_SECTION {
		Ignore, AppID, AppName, IconMD5
	};

	private IconCache m_oIconCache; // This is needed as the class created
									// Application-instances, which in turn need
									// a reference to the IconCache...
	private String m_sInResponseToTag;
	private E_CURRENT_SECTION p_eCurrentSection;

	private String m_sCurrentID, m_sCurrentName, m_sCurrentMD5;
	private List<Application> m_lstApps;

	_ResponseParser_GET_APPS(IconCache p_oIconCache) {
		m_oIconCache = p_oIconCache;
	}

	/**
	 * Returns the resulting list of available displays
	 * 
	 * @return
	 */
	public List<Application> getApps() {
		// Sanitize response
		if (m_sInResponseToTag
				.equals(DisperInterface.E_DISPER_PROTOCOL.GET_APPLICATIONS
						.toString()))
			return m_lstApps;
		else
			return null;
	}

	/* ******************** PARSER CALLBACKS ****************** */
	@Override
	public void startDocument() throws SAXException {
		// m_bInReponseTag = false;
		m_lstApps = new ArrayList<Application>();
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			org.xml.sax.Attributes attributes) throws SAXException {

		if (localName.equals(DisperInterface.XML_RESPONSE_TAG)) {
			m_sInResponseToTag = attributes
					.getValue(DisperInterface.XML_RESPONSE_COMMAND_ATTR);
		} else if (localName.equals(DisperInterface.XML_APP_ID_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.AppID;
		else if (localName.equals(DisperInterface.XML_APP_NAME_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.AppName;
		else if (localName.equals(DisperInterface.XML_APP_ICON_MD5_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.IconMD5;
	}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException {
		if (localName.equals(DisperInterface.XML_APPLICATION_TAG)) {
			// Add the display to the list
			m_lstApps.add(new Application(m_oIconCache, m_sCurrentID,
					m_sCurrentName, m_sCurrentMD5));
			m_sCurrentID = null;
			m_sCurrentName = null;
			m_sCurrentMD5 = null;
		}
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void characters(char ch[], int start, int length) {
		String sText = new String(ch, start, length);
		sText = sText.trim();

		if (p_eCurrentSection == E_CURRENT_SECTION.AppID)
			m_sCurrentID = sText;
		else if (p_eCurrentSection == E_CURRENT_SECTION.AppName)
			m_sCurrentName = sText;
		else if (p_eCurrentSection == E_CURRENT_SECTION.IconMD5)
			m_sCurrentMD5 = sText;
	}
}

// ---------------------------------------------------------------

// The class parses a response from the GET_APPLICATIONS command
class _ResponseParser_REQUEST_ICON extends DefaultHandler {
	private final static String TAG = "_ResponseParser_REQUEST_ICON";

	private enum E_CURRENT_SECTION {
		Ignore, Icon, IconType
	};

	private String m_sInResponseToTag;
	private E_CURRENT_SECTION p_eCurrentSection;

	private String m_sIconType, m_sIcon_MD5;
	private StringBuilder m_sbIcon_Base64;

	/**
	 * Returns the resulting list of available displays
	 * 
	 * @return
	 */
	public String getIcon_Base64() {
		// Sanitize response
		if (m_sInResponseToTag
				.equals(DisperInterface.E_DISPER_PROTOCOL.REQUEST_APP_ICON
						.toString())) {
			Log.v(TAG, "(II) About to return icon(base64), size:"
					+ m_sbIcon_Base64.length());
			return m_sbIcon_Base64.toString();
		} else
			return null;
	}

	public String getIconType() {
		return m_sIconType;
	}

	public String getIcon_MD5() {
		return m_sIcon_MD5;
	}

	/* ******************** PARSER CALLBACKS ****************** */
	@Override
	public void startDocument() throws SAXException {
		// m_bInReponseTag = false;
		m_sbIcon_Base64 = new StringBuilder();
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			org.xml.sax.Attributes attributes) throws SAXException {

		if (localName.equals(DisperInterface.XML_RESPONSE_TAG)) {
			m_sInResponseToTag = attributes
					.getValue(DisperInterface.XML_RESPONSE_COMMAND_ATTR);
			m_sIcon_MD5 = attributes
					.getValue(DisperInterface.XML_APP_ICON_MD5_ATTR);
		} else if (localName.equals(DisperInterface.XML_APP_ICON_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.Icon;
		else if (localName.equals(DisperInterface.XML_APP_ICON_TYPE_TAG))
			p_eCurrentSection = E_CURRENT_SECTION.IconType;
	}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException {
		p_eCurrentSection = E_CURRENT_SECTION.Ignore;
	}

	@Override
	public void characters(char ch[], int start, int length) {
		String sText = new String(ch, start, length);
		sText = sText.trim();

		if (p_eCurrentSection == E_CURRENT_SECTION.Icon)
			m_sbIcon_Base64.append(sText);
		else if (p_eCurrentSection == E_CURRENT_SECTION.IconType)
			m_sIconType = sText;
	}
}
